/*--------------------------------------------------------------------------------------*\
**
** adv_altar.cpp
**
** Heroes IV
** Copyright 2000, The 3DO Company
**
\*--------------------------------------------------------------------------------------*/

// kill the 255+ character identifier warning.  Templates tend to exceed that easily.
#include "precompiled.h"
#pragma warning( disable: 4786 )

#include "adv_altar.h"

#include "adventure_frame.h"
#include "adventure_map.h"
#include "adventure_sounds.h"
#include "adv_object_type.h"
#include "ai_value.h"
#include "army.h"
#include "basic_dialog.h"
#include "bitmap_group_cache.h"
#include "bitset_io.h"
#include "dialog_altar.h"
#include "enum_operations.h"
#include "hero.h"
#include "music.h"
#include "object_registration.h"
#include "player.h"
#include "random.h"
#include "simple_dialog.h"
#include "skill_properties.h"
#include "stationary_adventure_object.h"

namespace
{
	int const k_current_random_map_format_version = 1;

	t_register_with_type<t_adv_altar> k_registration( k_adv_object_teacher );
	t_object_registration<t_random_altar> k_random_registration( k_adv_object_random_teacher );

	char const * const k_model_name[] =
	{ 
		"Scholars.Tactician", 
		"Scholars.Combat", 
		"Scholars.Scout", 
		"Scholars.Noble", 
		"Scholars.Priest", 
		"Scholars.Scholar", 
		"Scholars.Lich", 
		"Scholars.Sorcerer", 
		"Scholars.Druid"
	};

	float const k_raw_bonus_for_altars = 100.0f;
}

t_skill_type hero_can_learn_more_skill( t_skill_type altar_skill, t_hero const* hero )
{
    t_skill_type            primary_skill;
    t_skill_type            skill;

    for (skill = k_skill_tactics; skill < k_skill_count; enum_incr( skill ))
    {
        primary_skill = get_primary_skill(skill);
        
        // Make sure this has the same primary skill.
        if (primary_skill != altar_skill)
            continue;

		t_skill_mastery mastery = hero->get_skill( skill );

        // Hero can't learn any more of this skill.
	    if (mastery == k_mastery_grand_master)
            continue;

		if (hero->can_learn( t_skill( skill, enum_add( mastery, 1 ) ) ))
			return skill;
    }

    return k_skill_none;
}

/*
** Altar/teacher.  Set type.  Teaches hero a skill.
*/
t_adv_altar::t_adv_altar(  std::string const& source, t_qualified_adv_object_type const& type )
          : t_stationary_adventure_object( source )
{
	m_altar_type = t_skill_type( type.major_subtype() );
}

t_adv_altar::t_adv_altar( t_skill_type skill )
                  : t_stationary_adventure_object( k_model_name[skill] ),
				    m_altar_type(skill)
{
	assert(	m_altar_type == (t_skill_type) get_major_subtype() );
}

void t_adv_altar::activate_trigger( t_army* army, t_adv_map_point const& point, 
		                           t_direction direction, t_adventure_frame* frame )
{
	assert(army);
	t_player* player = army->get_owner();
	if (player == NULL) return;

	if (player->is_computer())
	{
		select_skill_for_computer( *army );
		return;
	}

	t_creature_array& creatures = army->get_creatures();
	int                     i;
	t_hero*                 hero;
	std::vector<t_hero*>    heroes;
    t_skill                 skill( m_altar_type, k_mastery_basic );
	bool				    any_heroes = false;
	bool				    hero_maxed_out = false;
	bool				    dead_heroes = true;
    std::vector<bool>       valid_hero;
            
    // Check for heroes.
	for (i = 0; i < t_creature_array::k_size; i++)
	{
		hero = creatures[i].get_hero();
		if (hero == 0)
			continue;

		any_heroes = true;
		if (hero->is_dead())
            continue;
	    dead_heroes = false;

        // Can hero learn?
		if ( hero->get_skill( skill.skill ) == k_mastery_none )
		{
            if (hero->get_primary_skill_count() >= k_primary_skill_limit)
            {
    	        hero_maxed_out = true;
                continue;
            }
        }
		else
		{
			// If hero has grandmaster in all of this skill, 
			// hero can't learn any more of skills of primary type "skill".
			if (hero_can_learn_more_skill( skill.skill, hero ) == k_skill_none)
			{
				hero_maxed_out = true;
				continue;
			}
		}

		heroes.push_back( hero );

	}

    std::string status_text;
    // No heroes in party.
	if (any_heroes == false)
    {
        status_text = get_text ("no_heroes");
        if (yes_no_dialog( status_text, get_dialog_bottom( point ) ))
            destroy();
        return;        
    }
    // No eligible heroes.
    if (heroes.size() == 0)
    {
        if (dead_heroes)
        {
            status_text = get_text ("dead_heroes");
        }
        /*
        ** Hero can't learn anymore.
        ** Hero has maxed out his primary skills...
        */
        else if (hero_maxed_out == true)
        {
            status_text = get_text ("denied");
        }
        else
        {
            status_text = get_text ("no_heroes");
        }
        
        if (yes_no_dialog( status_text, get_dialog_bottom( point ) ))
            destroy();
        return;
    }

    // Select hero for upgrade.
	t_counted_ptr<t_dialog_altar> dialog_ptr;

	dialog_ptr = new t_dialog_altar( frame );
	dialog_ptr->init_dialog( heroes, m_altar_type, this );

    // Did player get benefit?
	t_sound_cache music = get_music_playing();
	stop_music();
	get_dialog_sound( k_dialog_sound_university )->play( get_sound_volume() );
	if (dialog_ptr->run_modal())
    {
        destroy();
    }
	play_music( music );
}

void t_adv_altar::select_skill_for_computer( t_army& army )
{
	t_skill_type	best_skill = k_skill_none;
	float			best_value = 0.0f;
	t_hero*			best_hero = NULL;

	for (int i = 0; i < t_creature_array::k_size; i++)
	{
		t_skill_mastery mastery;
		t_hero* hero = army[i].get_hero();

		if ((hero == NULL) || hero->is_dead())
			continue;

        // Can hero learn?
		if ( (mastery = hero->get_skill( m_altar_type )) == k_mastery_none )
		{
            if (!ai_wants_primary_skill(hero, m_altar_type, &army) || (hero->get_primary_skill_count() >= k_primary_skill_limit))
				continue;

			t_skill skill( m_altar_type, enum_add(mastery, 1));
			float value = ai_value_of_skill_increase( hero, skill, &army ) + k_raw_bonus_for_altars;
			if (value > best_value) 
			{
				best_value = value;
				best_hero = hero;
				best_skill = m_altar_type;
			}
            continue;
        }
		else
		{
			t_skill_type skill = hero_can_learn_more_skill( m_altar_type, hero );
			if (skill != k_skill_none)
			{
				t_skill new_skill( skill, enum_add(hero->get_skill( skill ), 1) );
				
				float value = ai_value_of_skill_increase( hero, new_skill, &army ) + k_raw_bonus_for_altars;
				if (value > best_value) 
				{
					best_value = value;
					best_hero = hero;
					best_skill = skill;
				}
			}
		}
	}

	if (best_hero != NULL) 
	{
		best_hero->learn_skill( t_skill( best_skill, enum_add( best_hero->get_skill( best_skill ), 1 ) ));
		best_hero->update_class();
	}
	else
	{
		assert(false);
		int delete_this_line_if_left_in = 0;
	}
	
	// It shouldn't get to this point without a valid hero available.
	destroy();
}

bool t_adv_altar::read( std::streambuf&						stream, 
				        t_qualified_adv_object_type const&	type,
					    int									version )
{
    m_altar_type = t_skill_type( get<t_uint32>( stream ));
    return true;
}

bool t_adv_altar::write( std::streambuf& stream ) const
{
    put<t_uint32>( stream, m_altar_type );
    return true;
}

float t_adv_altar::ai_value( t_adventure_ai const& ai, t_creature_array const& army, int move_cost ) const
{
	float best_value = 0.0f;
	for (int i = 0; i < t_creature_array::k_size; i++)
	{
		t_skill_mastery mastery;
		t_hero const* hero = army[i].get_hero();

		if ((hero == NULL) || hero->is_dead())
			continue;

        // Can hero learn?
		if ( (mastery = hero->get_skill( m_altar_type )) == k_mastery_none )
		{
            if (ai_wants_primary_skill(hero, m_altar_type, &army) && (hero->get_primary_skill_count() < k_primary_skill_limit))
            {
				t_skill skill( m_altar_type, enum_add(mastery, 1));
				
				float value = ai_value_of_skill_increase( hero, skill, &army ) + k_raw_bonus_for_altars;
				if (value > best_value) 
					best_value = value;
            }
        }
		else
		{
			t_skill_type skill = hero_can_learn_more_skill( m_altar_type, hero );
			if (skill != k_skill_none)
			{
				t_skill new_skill( skill, enum_add(hero->get_skill( skill ), 1) );
				
				float value = ai_value_of_skill_increase( hero, new_skill, &army ) + k_raw_bonus_for_altars;
				if (value > best_value) 
					best_value = value;
			}
		}
	}
	return best_value;
}

//
//
// Random Altar
//
//

t_random_altar::t_random_altar( std::string const& model_name )
	: t_stationary_adventure_object( model_name ),
	  m_available_skills( ~t_primary_skill_set() ),
	  m_skill( k_skill_life_magic )
{
}

// -------------------------------------------------------
// -------------------------------------------------------
bool t_random_altar::read_from_map(
	std::streambuf &		buffer,
	t_progress_handler * )
{
	int version = get< t_uint16 >( buffer );
	if ( version < 0 || version > k_current_random_map_format_version )
		return false;

	if ( version > 0 )
	{
		m_available_skills = ::get_bitset< k_skill_primary_count >( buffer );
		if ( !m_linkage_info.read_from_map( buffer ) )
			return false;
	}
	else
	{
		m_available_skills = ~t_primary_skill_set();
		m_linkage_info.reset();
	}

	return true;
}

// -------------------------------------------------------
// -------------------------------------------------------
bool t_random_altar::preplacement( t_adventure_map& map, int pass )
{
	if (t_stationary_adventure_object::preplacement( map, pass ))
		return true;

	t_primary_skill_set alignment_skills;

	switch( m_linkage_info.get_link_alignment( map, pass ) )
	{
		case t_linkage_data::k_link_alignment_life:
			alignment_skills[k_skill_life_magic] = true;
			break;

		case t_linkage_data::k_link_alignment_order:
			alignment_skills[k_skill_order_magic] = true;
			break;

		case t_linkage_data::k_link_alignment_death:
			alignment_skills[k_skill_death_magic] = true;
			break;

		case t_linkage_data::k_link_alignment_chaos:
			alignment_skills[k_skill_chaos_magic] = true;
			break;

		case t_linkage_data::k_link_alignment_nature:
			alignment_skills[k_skill_nature_magic] = true;
			break;

		case t_linkage_data::k_link_alignment_might:
			alignment_skills[k_skill_tactics] = true;
			alignment_skills[k_skill_combat_defense] = true;
			alignment_skills[k_skill_scouting] = true;
			alignment_skills[k_skill_nobility] = true;
			break;

		case t_linkage_data::k_link_alignment_any:
			alignment_skills = ~t_primary_skill_set();
			break;

		case t_linkage_data::k_link_alignment_wait:
			return true; 

		default:
			assert(false);
			alignment_skills = ~t_primary_skill_set();
			break;
	}

	t_primary_skill_set possible_skills = (alignment_skills & m_available_skills);

	if ( possible_skills.none() )
	{
		// If the editor has forbidden the alignment's skills, choose one of
		// the allowable skills. Note that this interpretation of the conflict
		// between alignment_skills and m_available_skills gives the map maker
		// the maximum flexability possible
		possible_skills = m_available_skills;

		// Editor shouldn't let this happen, but just in case
		if ( possible_skills.none() )
		{
			possible_skills = alignment_skills;
		}
	}

	int index = random( possible_skills.count() );

	m_skill = t_skill_type(0);

	while ( !possible_skills[m_skill] )
		enum_incr(m_skill);

	while (index)
	{
		enum_incr(m_skill);

		while ( !possible_skills[m_skill] )
			enum_incr(m_skill);

		index--;
	}
	
	return false;
}

// -------------------------------------------------------
// -------------------------------------------------------
void t_random_altar::place( t_adventure_map& map, t_adv_map_point const& point )
{
	t_adv_altar*		altar;

	altar = new t_adv_altar( m_skill );
	altar->place( map, point );
}

